using System;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Health.ItemTypes;

namespace Microsoft.Health.Samples.PatientConnect.ContosoHealth.HealthVaultAdapter
{
    /// <summary>
    /// This class is responsible for generating the Health Vault DataTypes in Xml format,
    /// This class generates the parsed HL7 message in xml format and generate the Xml equivalent of LabTestResults
    /// </summary>
    public class LabResults
    {
        XmlDocument _xmldoc;
        XPathNodeIterator _iterator;
        LabTestResults _labTestResults = new LabTestResults();
        LabTestResultGroup _labGroup = new LabTestResultGroup();
        string _extensionXml;

        /// <summary>
        /// Create Lab Test Results
        /// </summary>
        /// <param name="labResultFilePath">lab test results file path</param>
        /// <param name="hl7MessageXml">Parsed xml file of HL7 message</param>
        public void CreateLabResult(string labResultFilePath,
                                    XmlDocument hl7MessageXml)
        {
            _xmldoc = hl7MessageXml;
            XPathNavigator navigator = _xmldoc.CreateNavigator();

            CreateLabGroup(navigator);
            CreateLabResults(navigator);
            CreateOrderBy(navigator);

            HealthRecordItemExtension extension = new HealthRecordItemExtension("Contoso Health");

            XmlDocument extensiondoc = new XmlDocument();
            extensiondoc.LoadXml(_extensionXml.ToString());
            _labTestResults.CommonData.Extensions.Add(extension);
            XPathNavigator extensionNavigator = extensiondoc.CreateNavigator();

            XmlDocument labTestDoc = new XmlDocument();
            labTestDoc.LoadXml(_labTestResults.GetItemXml("lab-test-results"));
            XPathNavigator labtestNav = labTestDoc.CreateNavigator();

            XPathNodeIterator iterator1 = labtestNav.Select("lab-test-results/data-xml/common");
            iterator1.MoveNext();
            iterator1.Current.InnerXml = extensionNavigator.Select("extension").Current.OuterXml;
            labTestDoc.Save(labResultFilePath);
        }

        /// <summary>
        /// Create OrderBy Node for LabTestResult datatype
        /// </summary>
        /// <param name="navigator">XpathNavigator for the parsed xml</param>
        private void CreateOrderBy(XPathNavigator navigator)
        {
            _labTestResults.OrderedBy = CreateOrderByContactInfo(navigator);
            Phone phone = CreateOrderByPhone(navigator);
            if (phone != null)
            {
                _labTestResults.OrderedBy.Contact.Phone.Add(phone);
            }
            Email email = CreateOrderByEmail(navigator);
            if (email != null)
            {
                _labTestResults.OrderedBy.Contact.Email.Add(email);
            }
        }
        /// <summary>
        /// Create email
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <returns>Email</returns>
        private Email CreateOrderByEmail(XPathNavigator navigator)
        {
            Email email = null;
            string emailAddress = string.Empty;
            emailAddress = GetXPathValue(navigator, "HL7/OBR[1]/OBR.17/OBR.17.4");
            if (!string.IsNullOrEmpty(emailAddress))
            {
                email = new Email(emailAddress, "", true);
            }
            return email;
        }
        /// <summary>
        /// Create Phone
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <returns>Phone</returns>
        private Phone CreateOrderByPhone(XPathNavigator navigator)
        {
            Phone phone = null;
            string phoneDesc = string.Empty;
            XPathNavigator nav = navigator.SelectSingleNode("HL7/OBR[1]/OBR.17");
            phoneDesc = GetXPathValue(nav, "OBR.17.7");
            string countryCode = GetXPathValue(nav, "OBR.17.5");
            string areaCode = GetXPathValue(nav, "OBR.17.6");
            string phoneNum = GetXPathValue(nav, "OBR.17.7");
            string extension = GetXPathValue(nav, "OBR.17.8");

            string phoneNumber = string.Format("({0})-(1)-{2}-{3}", countryCode, areaCode, phoneNum, extension);
            phone = new Phone(phoneNumber, phoneDesc, true);
            return phone;
        }

        /// <summary>
        /// Create Contact Info
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <returns>Organization</returns>
        private Organization CreateOrderByContactInfo(XPathNavigator navigator)
        {
            Organization orderedOrganization = new Organization();

            string providerFamilyName = GetXPathValue(navigator, "HL7/OBR[1]/OBR.16/OBR.16.2");
            string providerGivenName = GetXPathValue(navigator, "HL7/OBR[1]/OBR.16/OBR.16.3");
            string orderbyName = string.Format("{0} {1}", providerGivenName, providerFamilyName);

            string orderedOrganizationDetail = GetXPathValue(navigator, "HL7/OBR[1]/OBX[1]/OBX.15/OBX.15.2");
            string[] orderedDelimitString ={ @"\T\" };
            string[] orderedOrgDetailArr = orderedOrganizationDetail.Split(orderedDelimitString, StringSplitOptions.RemoveEmptyEntries);
            orderedOrganization.Name = orderbyName.ToString();
            ContactInfo orderedcontact = CreateAddress(orderedOrganizationDetail, orderedOrgDetailArr);
            orderedOrganization.Contact = orderedcontact;

            return orderedOrganization;
        }

        /// <summary>
        /// Create Contact Info
        /// </summary>
        /// <param name="orderedOrganizationDetail">organization detail</param>
        /// <param name="orderedogrDetailArr">Organization detail array</param>
        /// <returns>Contact Info</returns>
        private ContactInfo CreateAddress(string orderedOrganizationDetail,
                                          string[] orderedOgrDetailArr)
        {
            ContactInfo orderedContact = new ContactInfo();

            Address orderedAddress = new Address();
            orderedAddress.Street.Add(orderedOgrDetailArr[1]);
            orderedAddress.City = orderedOgrDetailArr[2];
            orderedAddress.Country = "US";
            orderedAddress.State = orderedOgrDetailArr[3];
            orderedAddress.PostalCode = orderedOgrDetailArr[4];
            orderedAddress.Description = orderedOrganizationDetail;
            orderedAddress.IsPrimary = true;

            orderedContact.Address.Add(orderedAddress);

            return orderedContact;
        }


        /// <summary>
        /// Create LabTestResult Node for LabTestResult data type
        /// </summary>
        /// <param name="navigator">XpathNavigator for the parsed xml</param>
        private void CreateLabResults(XPathNavigator navigator)
        {
            _iterator = navigator.Select("HL7/OBR");
            for (int currentObr = 1; currentObr <= _iterator.Count; currentObr++)
            {
                XPathNodeIterator obxiterator = _iterator.Current.Select("HL7/OBR[" + currentObr + "]/OBX");
                for (int currentobx = 1; currentobx <= obxiterator.Count; currentobx++)
                {
                    LabTestResultDetails labResults = new LabTestResultDetails();

                    SetObservationDate(navigator, currentObr, currentobx, labResults);
                    labResults.Substance = CreateCodableValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.15/OBR.15.1", "OBR.15.1.2", "OBR.15.1.3", "OBR.15.1.1", "Substance");
                    labResults.CollectionMethod = CreateCodableValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.15/OBR.15.6", "OBR.15.6.2", "OBR.15.6.3", "OBR.15.6.1", "CollectionMethod");
                    labResults.ClinicalCode = CreateCodableValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.3", "OBX.3.2", "OBX.3.3", "OBX.3.1", "ClinicalCode");
                    labResults.Name = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.3/OBX.3.2");

                    string text = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.11");
                    labResults.Status = CreateCodableValue(text, text, string.Empty, "ResultStatus");

                    labResults.Value = SetLabResultValue(navigator, currentObr, currentobx, labResults);

                    string testrange = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.7");
                    string[] rangeArr = testrange.Split('-');
                    TestResultRange resultRanges = new TestResultRange();
                    DoubleRange range = new DoubleRange();

                    double min = 0.0;
                    double max = 0.0;
                    double.TryParse(rangeArr[0], out min);
                    double.TryParse(rangeArr[1], out max);
                    range.MinRange = min;
                    range.MaxRange = max;

                    resultRanges.RangeType = CreateCodableValue("Range", "range", string.Empty, "Range");
                    resultRanges.Range = range;
                    labResults.Value.Ranges.Add(resultRanges);

                    CodableValue flagCodableValue = new CodableValue();
                    string flagText = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.8");
                    flagCodableValue = CreateCodableValue(flagText, flagText, string.Empty, "FlagCode");
                    if (flagCodableValue != null)
                    {
                        labResults.Value.Flag.Add(flagCodableValue);
                    }
                    _labTestResults.Groups[currentObr - 1].Results.Add(labResults);
                }
            }
        }
        /// <summary>
        /// Set lab result value
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <param name="currentObr">current obr index</param>
        /// <param name="currentobx">current obx index</param>
        /// <param name="labResults">lab Result Details</param>
        /// <returns>Lab Test Result value</returns>
        private LabTestResultValue SetLabResultValue(XPathNavigator navigator,
                                                     int currentObr,
                                                     int currentobx,
                                                     LabTestResultDetails labResults)
        {
            StructuredMeasurement structMesaurement = new StructuredMeasurement();
            string codeValue = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.6/OBX.6.2");
            string value = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.5");
            structMesaurement.Units = CreateCodableValue("Units", codeValue, "Units", "StructureMeasurement");
            structMesaurement.Value = Convert.ToDouble(value);
            LabTestResultValue labValue = new LabTestResultValue();
            GeneralMeasurement measurement = new GeneralMeasurement();
            measurement.Display = value;
            measurement.Structured.Add(structMesaurement);
            labValue.Measurement = measurement;
            return labValue;

        }

        /// <summary>
        /// Create codable value
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <param name="codedXpath">xpath for code</param>
        /// <param name="textXpath">xpath for text</param>
        /// <param name="familyXpath">xpath for family</param>
        /// <param name="valueXpath">xpath for value</param>
        /// <param name="vocabularyName">vocabulary name</param>
        /// <returns>Codabel value</returns>
        private CodableValue CreateCodableValue(XPathNavigator navigator,
                                                string codedXpath,
                                                string textXpath,
                                                string familyXpath,
                                                string valueXpath,
                                                string vocabularyName)
        {
            XPathNavigator nav = navigator.SelectSingleNode(codedXpath);
            CodableValue codableValue = null;
            CodedValue codedValue = null;
            if (nav != null)
            {
                string family = string.Empty;
                string text = nav.SelectSingleNode(textXpath).Value;
                string value = nav.SelectSingleNode(valueXpath).Value;
                if (!string.IsNullOrEmpty(text))
                {
                    codableValue = new CodableValue();
                    codedValue = new CodedValue();
                    if (!string.IsNullOrEmpty(familyXpath))
                    {
                        family = nav.SelectSingleNode(familyXpath).Value;
                    }
                    if (!string.IsNullOrEmpty(vocabularyName))
                    {
                        codedValue.VocabularyName = vocabularyName;
                    }

                    codableValue.Text = text;
                    codedValue.Value = value;
                    codedValue.Family = family;

                    codableValue.Add(codedValue);
                }
            }
            return codableValue;
        }

        /// <summary>
        /// Create codable value
        /// </summary>
        /// <param name="text">text</param>
        /// <param name="codeValue">code vlaue</param>
        /// <param name="family">family</param>
        /// <param name="vocabularyName">vocabulary</param>
        /// <returns>Codable value</returns>
        private CodableValue CreateCodableValue(string text,
                                                string codeValue,
                                                string family,
                                                string vocabularyName)
        {
            CodableValue codableValue = null;
            CodedValue codedValue = null;

            if (!string.IsNullOrEmpty(text))
            {
                codableValue = new CodableValue();
                codedValue = new CodedValue();
                if (!string.IsNullOrEmpty(vocabularyName))
                {
                    codedValue.VocabularyName = vocabularyName;
                }
                codableValue.Text = text;
                codedValue.Value = codeValue;
                codedValue.Family = family;
                codableValue.Add(codedValue);
            }
            return codableValue;
        }

        /// <summary>
        /// Set observation date time
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <param name="currentObr">current obr</param>
        /// <param name="currentobx">current obx</param>
        /// <param name="labResults">lab result details</param>
        private void SetObservationDate(XPathNavigator navigator,
                                        int currentObr,
                                        int currentobx,
                                        LabTestResultDetails labResults)
        {
            string dateTimeString = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[" + currentobx + "]/OBX.19");
            if (!string.IsNullOrEmpty(dateTimeString))
            {
                DateTime approxDateTime = ParserDateTime(dateTimeString);
                ApproximateDate date = new ApproximateDate(approxDateTime.Year, approxDateTime.Month, approxDateTime.Day);
                ApproximateTime time = new ApproximateTime(approxDateTime.Hour, approxDateTime.Minute, approxDateTime.Second);
                ApproximateDateTime dateTime = new ApproximateDateTime(date, time);
                labResults.When = dateTime;
            }
        }

        /// <summary>
        /// Create LabGroups for LabTestResult data type
        /// </summary>
        /// <param name="navigator">XpathNavigator for the parsed xml</param>
        private void CreateLabGroup(XPathNavigator navigator)
        {
            XPathNodeIterator iterator = navigator.Select("HL7/OBR");
            for (int currentObr = 1; currentObr <= iterator.Count; currentObr++)
            {
                _labGroup = new LabTestResultGroup();
                SetLabGroupDateTime(navigator);

                //set lab group for the lab result
                string groupText = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.4/OBR.4.2");
                string groupFamily = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.4/OBR.4.3");
                _labGroup.GroupName = CreateCodableValue(groupText, groupText, groupFamily, "GroupName");

                // Set laboratory name here
                Organization organization = new Organization();
                string organizationDetail = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBX[1]/OBX.15/OBX.15.2");
                string[] delimitString ={ @"\T\" };
                string[] ogrDetailArr = organizationDetail.Split(delimitString, StringSplitOptions.RemoveEmptyEntries);
                organization.Name = ogrDetailArr[0];
                organization.Contact = CreateAddress(organizationDetail, ogrDetailArr);
                _labGroup.LaboratoryName = organization;

                //set status here
                string statusText = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.25");
                _labGroup.Status = CreateCodableValue(statusText, statusText, "LOINC", "GroupStatus");

                _labTestResults.Groups.Add(_labGroup);

                //Add extensions to the object, extension contains vital information which does not have mapping in the lab results xsd.
                //like sample collection volume, specimen recieve date, sepcimen quantity and time
                _extensionXml = SetExtensionXml(navigator, currentObr);

            }
        }
        /// <summary>
        /// Set Extension xml
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <param name="currentObr">current obr</param>
        /// <returns>extension xml</returns>
        private string SetExtensionXml(XPathNavigator navigator,
                                       int currentObr)
        {
            string extension = string.Empty;
            string setId = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.1");
            string collectionVolume = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.9");
            string collectionIdentifier = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.10");
            string specimentRecievedDate = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.14");
            string quantityAndTiming = GetXPathValue(navigator, "HL7/OBR[" + currentObr + "]/OBR.27");
            extension = string.Format("<additional-labgroup-data><extension SetId =\"{0}\" >{1}{2}{3}{4}</extension></additional-labgroup-data>",
                setId,
                CreateExtensionXML("collectionvolume", collectionVolume),
                CreateExtensionXML("collectionidentifier", collectionIdentifier),
                CreateExtensionXML("specimenreceiveddate", specimentRecievedDate),
                CreateExtensionXML("quantityandtiming", quantityAndTiming)
                );
            return extension;
        }
        /// <summary>
        /// Set LabGroupDateTime
        /// </summary>
        /// <param name="navigator">navigator</param>
        private void SetLabGroupDateTime(XPathNavigator navigator)
        {
            string dateTimeString = GetXPathValue(navigator, "HL7/OBR[1]/OBR.7");
            DateTime approxDateTime = ParserDateTime(dateTimeString);
            ApproximateDate date = new ApproximateDate(approxDateTime.Year, approxDateTime.Month, approxDateTime.Day);
            ApproximateTime time = new ApproximateTime(approxDateTime.Hour, approxDateTime.Minute, approxDateTime.Second);
            ApproximateDateTime dateTime = new ApproximateDateTime(date, time);
            _labTestResults.When = dateTime;
        }

        /// <summary>
        /// Create Extensions that contains important information that does not maps to the LabTestResult XSD
        /// </summary>
        /// <param name="navigator">XpathNavigator for the parsed xml</param>
        private string CreateExtensionXML(string nodeName,
                                          string nodeValue)
        {
            string extensiondata = string.Format("<{0}>{1}</{0}>", nodeName, nodeValue);
            return extensiondata.ToString();
        }

        /// <summary>
        /// Get Xpath value
        /// </summary>
        /// <param name="navigator">navigator</param>
        /// <param name="xpath">xpath</param>
        /// <returns>xpath value</returns>
        private string GetXPathValue(XPathNavigator navigator,
                                     string xpath)
        {
            string xpathValue = string.Empty;
            XPathNavigator nav = navigator.SelectSingleNode(xpath);
            if (nav != null)
            {
                xpathValue = nav.Value;
            }
            return xpathValue;
        }

        /// <summary>
        /// Parses the date time in Date time format
        /// </summary>
        /// <param name="dateTimeString">date time string</param>
        /// <returns>Parsed DateTime</returns>
        private DateTime ParserDateTime(string dateTimeString)
        {
            DateTime approxDateTime = new DateTime();
            string[] dateFormats = new string[]{
                "yyyyMMdd",
                "yyyyMMddHHmmsszzz",
                "yyyyMMddHHmmss"
            };
            approxDateTime = DateTime.ParseExact(dateTimeString, dateFormats, null, System.Globalization.DateTimeStyles.None);
            return approxDateTime;
        }
    }
}